﻿using AdvVerifyGUI.Display;
using AdvVerifyModels.Entities;
using HWModule;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows.Media;

namespace AdvVerifyGUI.Job;

interface IJobContext
{
    Task Write(ImagingResult img, object data = null);

    bool IsCanceled { get; }
}

class JobContext : IJob, IJobContext
{
    [DllImport("Metrology.dll")]
    public extern static int CDMeasureTestMulti(ref AMPParam measureOptionBuf, ref PositionBuf positionBuf, int posNum, byte[] pImage, int imgx, int imgy, float pixelSize, ref DrawOption drawOption,
                                    byte[] pResultpImage, ref CDResult pCDPointBuf);

    [DllImport("Metrology.dll")]
    public extern static int CDMeasureTestAuto(ref AMPParam measureOptionBuf, byte[] pImage, int imgx, int imgy, float pixelSize, ref DrawOption drawOption,
                                    byte[] pResultpImage, ref CDResult pCDPointBuf);
    readonly JobQueue queue;
    readonly FrameDispatcher dispatcher;
    readonly JobRunOptions options;

    readonly Channel<(ImagingResult, object)> imagingChan = Channel.CreateUnbounded<(ImagingResult, object)>();

    readonly TaskCompletionSource confirmation;

    volatile bool isCanceled;
    volatile bool done;

    public JobContext(JobQueue queue,
        FrameDispatcher dispatcher,
        JobRunOptions options,
        bool requireConfirmation)
    {
        this.queue = queue;
        this.dispatcher = dispatcher;
        this.options = options;

        if (requireConfirmation)
        {
            confirmation = new();
        }
    }

    public Task Completion => imagingChan.Reader.Completion;

    public Task Confirmation
    {
        get
        {
            return confirmation == null ? Task.CompletedTask : confirmation.Task;
        }
    }

    public bool IsCanceled => isCanceled;

    public void Cancel()
    {
        isCanceled = true;
        confirmation?.TrySetResult();
    }

    public void Confirm()
    {
        if (queue.IsBusy)
        {
            throw new InvalidOperationException("Job queue is busy.");
        }
        confirmation?.TrySetResult();
    }

    public async Task Write(ImagingResult img, object data = null)
    {
        await imagingChan.Writer.WriteAsync((img, data)).ConfigureAwait(false);
    }

    public void Complete()
    {
        done = true;
        imagingChan.Writer.Complete();
    }

    public void InternalCancel(Exception ex)
    {
        isCanceled = true;
        imagingChan.Writer.TryComplete(ex);
    }

    public async IAsyncEnumerable<(ImagingResult, (ImageData, double)[])> RunProcessingPipeline()
    {
        await foreach (var (pImg, data) in imagingChan.Reader.ReadAllAsync().ConfigureAwait(false))
        {
            var pattern = pImg.Pattern;
            switch (pImg.Kind)
            {
                // OM frames already dispatched
                case PatternKind.High:
                    yield return (pImg, []);
                    break;
                case PatternKind.Align:
                case PatternKind.Addr:
                    var raw = pImg.Image;
                    var marker = DrawMarker(pImg.Image, pImg.MatchDx, pImg.MatchDy);
                    pImg.Image = marker;
                    Dispatch(pImg.Image, 500);
                    yield return (pImg, [(raw, 0)]);
                    break;
                case PatternKind.PreDose:
                case PatternKind.Focus:
                    Dispatch(pImg.Image, data, 30);
                    break;
                case PatternKind.Mp:
                    if (pImg.Score < pattern.Reference.Threshold || options.SkipMeasurement)
                    {
                        Dispatch(pImg.Image, 500);
                        yield return (pImg, []);
                    }
                    else
                    {
                        var values = new (ImageData, double)[pattern.CDPoints.Count];
                        if (pattern.CDPoints.Count == 0)
                        {
                            Dispatch(pImg.Image, 500);
                        }

                        for (var i = 0; i < pattern.CDPoints.Count; i++)
                        {
                            var spec = pattern.CDPoints[i];
                            var mp = pImg.Image;
                            var param = spec.AMPParam;
                            var settings = pattern.Reference.ScanSettings;
                            var pixelSize = settings.FOV / settings.ScanWidth * 1000;
                            var copy = new byte[mp.Data.Length];
                            mp.Data.CopyTo(copy, 0);
                            var drawOptions = spec.DrawOption;
                            var result = new CDResult();
                            if (param.ACD == 0)
                            {
                                var posBuf = spec.PositionBuf;
                                posBuf.CDPos = new PositionInfo[posBuf.CDPos.Length];
                                for (var j = 0; j < param.target; j++)
                                {
                                    posBuf.CDPos[j] = spec.PositionBuf.CDPos[j];
                                    switch (param.measureType)
                                    {
                                        case 0:
                                        case 1:
                                        case 2:
                                            posBuf.CDPos[j].ix0 = Math.Clamp(posBuf.CDPos[j].ix0 + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].ix1 = Math.Clamp(posBuf.CDPos[j].ix1 + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].iy0 = Math.Clamp(posBuf.CDPos[j].iy0 + (int)pImg.MatchDy, param.sumLinePoint, settings.ScanWidth - 1 - param.sumLinePoint);
                                            posBuf.CDPos[j].iy1 = Math.Clamp(posBuf.CDPos[j].iy1 + (int)pImg.MatchDy, param.sumLinePoint, settings.ScanWidth - 1 - param.sumLinePoint);
                                            break;
                                        case 3:
                                            posBuf.CDPos[j].x_center = Math.Clamp(posBuf.CDPos[j].x_center + (int)pImg.MatchDx, 0, settings.ScanWidth - 1);
                                            posBuf.CDPos[j].y_center = Math.Clamp(posBuf.CDPos[j].y_center + (int)pImg.MatchDy, 0, settings.ScanWidth - 1);
                                            break;
                                    }
                                }
                                _ = CDMeasureTestMulti(ref param, ref posBuf, param.target, mp.Data, mp.Width, mp.Height, (float)pixelSize, ref drawOptions, copy, ref result);
                            }
                            else
                            {
                                _ = CDMeasureTestAuto(ref param, mp.Data, mp.Width, mp.Height, (float)pixelSize, ref drawOptions, copy, ref result);
                            }

                            var img = new ImageData(mp.Width, mp.Height, copy, PixelFormats.Gray8);
                            Dispatch(img, 500);

                            double res;
                            switch (param.measureType)
                            {
                                case 1:
                                    res = result.sigma;
                                    break;
                                case 2:
                                    res = (result.sigmaLeftEdge + result.sigmaRightEdge) / 2;
                                    break;
                                default:
                                    res = result.average;
                                    break;
                            }
                            res *= pixelSize;
                            values[i] = (img, res);
                        }
                        yield return (pImg, values);
                    }
                    break;
            }
        }
    }

    void Dispatch(ImageData img, int time)
    {
        Dispatch(img, null, time);
    }

    void Dispatch(ImageData img, object data, int time)
    {
        if (options.SuppressImagesAfterCompletion && done)
        {
            return;
        }

        dispatcher.Dispatch(img, data, time);
    }

    static ImageData DrawMarker(ImageData img, double dx, double dy)
    {
        var (width, height, b, _) = img;
        var data = new byte[b.Length];
        Array.Copy(b, data, data.Length);
        int centerX = (int)(width / 2 + dx);
        int centerY = (int)(height / 2 + dy);
        int crossSize = Math.Min(width, height) / 25;
        int crossThickness = 2;

        for (int y = centerY - crossThickness; y <= centerY + crossThickness; y++)
        {
            if (y < 0 || y >= height) continue;
            for (int x = centerX - crossSize; x < centerX + crossSize; x++)
            {
                if (x < 0 || x >= width) continue;
                int index = y * width + x;
                if (index < data.Length)
                {
                    data[index] = 255;
                }
            }
        }

        for (int x = centerX - crossThickness; x <= centerX + crossThickness; x++)
        {
            if (x < 0 || x >= width) continue;
            for (int y = centerY - crossSize; y < centerY + crossSize; y++)
            {
                if (y < 0 || y >= height) continue;
                int index = y * width + x;
                if (index < data.Length)
                {
                    data[index] = 255;
                }
            }
        }
        return new ImageData(width, height, data, img.Format);
    }
}